﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Restful.Net.Reflection
{
    /// <summary>
    /// Class that holds information about a type that is a rest service.
    /// Is being used to select the correct method to invoke based on the incoming uri and http verb.
    /// </summary>
    public class ServiceTypeMap
    {
        private readonly Dictionary<HttpVerb, List<RestMethodInfo>> methodMap;

        private readonly Type serviceType;

        private readonly string baseUrl;

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceTypeMap"/> class.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="baseUrl">The base URL.</param>
        public ServiceTypeMap(Type serviceType, string baseUrl)
        {
            this.serviceType = serviceType;
            this.baseUrl = baseUrl;
            methodMap = new Dictionary<HttpVerb, List<RestMethodInfo>>();
            Init();
        }

        public MethodMatch GetMethod(Uri baseUri, Uri uri, HttpVerb verb)
        {
            if (!methodMap.ContainsKey(verb))
            {
                return null;
            }

            IOrderedEnumerable<MethodMatch> methodMatchs = methodMap[verb].Select(mx => new MethodMatch
                                                                                            {
                                                                                                Match = mx.Template.Match(baseUri, uri),
                                                                                                RestMethod = mx
                                                                                            }).Where(mxt => mxt.Match != null).OrderByDescending(GetMethodPriorityByNumberOfMatchingParameters);

            List<MethodMatch> matches = methodMatchs.ToList();
            return matches.FirstOrDefault();


        }

        private int GetMethodPriorityByNumberOfMatchingParameters(MethodMatch mxt)
        {
            int boundParameters = 0;
            foreach (string boundName in mxt.Match.BoundVariables)
            {
                string name = boundName;
                if (mxt.RestMethod.Parameters.Find(pi => pi.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null)
                {
                    boundParameters++;
                }

            }

            foreach (string boundName in mxt.Match.QueryParameters)
            {
                string name = boundName;
                if (mxt.RestMethod.Parameters.Find(pi => pi.Name.Equals(name, StringComparison.OrdinalIgnoreCase)) != null)
                {
                    boundParameters++;
                }
            }

            return boundParameters;
        }

        private void Init()
        {
            foreach (MethodInfo mi in serviceType.GetMethods())
            {
                object[] customAttributes = mi.GetCustomAttributes(typeof(RestMethodAttribute), false);
                if (customAttributes.Length == 0)
                {
                    continue;
                }

                RestMethodAttribute rm = (RestMethodAttribute)customAttributes[0];

                if (!methodMap.ContainsKey(rm.Verb))
                {
                    methodMap.Add(rm.Verb, new List<RestMethodInfo>());
                }

                string url = baseUrl + rm.UriTemplate;
                url = url.Replace("//", "/");

                methodMap[rm.Verb].Add(new RestMethodInfo(mi, new UriTemplate(url)));
            }
        }
    }
}